package com.freedom.sql.jdbc.utils;

import java.io.UnsupportedEncodingException;
/**
 * 
 * @author zhiqiang.liu
 * @2016年6月18日
 *
 */
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.BitSet;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import io.netty.buffer.ByteBuf;

public class ByteUtils {// 处理各种字符串读取的操作,都是从当前位置开始处理,根据大端小端来处理
	// 专门用于处理字节流
	// 本类需要优化，做字节边界安全控制，TODO
	// 下面主要用作读
	private static final Logger logger = LogManager.getLogger(ByteUtils.class);

	public static int verify(byte value) {
		if (value < 0) {
			return (int) (256 + value);
		}
		return value;
	}

	public static final int getVarLength(byte[] src) {
		int length = src.length;
		if (length < 251) {
			return 1 + length;
		} else if (length < 0x10000L) {
			return 3 + length;
		} else if (length < 0x1000000L) {
			return 4 + length;
		} else {
			return 9 + length;
		}
	}

	public static byte[] encode(String src, String charset) {
		if (src == null) {
			return null;
		}
		try {
			return src.getBytes(charset);
		} catch (UnsupportedEncodingException e) {
			return src.getBytes();
		}
	}

	public static String decode(byte[] src, String charset) {
		if (src == null) {
			return null;
		}
		try {
			return new String(src, charset);
		} catch (UnsupportedEncodingException e) {
			return new String(src);
		}
	}

	@SuppressWarnings("unused")
	private static final ThreadLocal<SimpleDateFormat> localDateFormat = new ThreadLocal<SimpleDateFormat>() {
		protected SimpleDateFormat initialValue() {
			return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		}
	};

	public static boolean validOffset(byte[] content, int length) {
		if (content == null) {
			LoggerUtils.error(logger, "content is null");
			return false;
		}
		if (content.length <= 0) {
			LoggerUtils.error(logger, "content length <=0");
			return false;
		}
		if (length < 0) {
			LoggerUtils.error(logger, "length <0");
			return false;
		}
		if (length > content.length - 1) {
			LoggerUtils.error(logger, "length >content.length -1");
			return false;
		}
		//
		// LoggerUtils.debug(logger, "valid");
		return true;
	}

	/**
	 * see mysql/strings/decimal.c
	 */
	public static long bigEndianLong(byte[] bytes, int offset, int length) {
		long result = 0;
		for (int i = offset; i < (offset + length); i++) {
			byte b = bytes[i];
			result = (result << 8) | (b >= 0 ? (int) b : (b + 256));
		}
		return result;
	}

	public static int numberOfBitsSet(BitSet bitSet) {
		int result = 0;
		for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
			result++;
		}
		return result;
	}

	@SuppressWarnings("unused")
	private static int[] split(long value, int divider, int length) {
		int[] result = new int[length];
		for (int i = 0; i < length - 1; i++) {
			result[i] = (int) (value % divider);
			value /= divider;
		}
		result[length - 1] = (int) value;
		return result;
	}

	@SuppressWarnings("unused")
	private static byte[] reverse(byte[] bytes) {
		for (int i = 0, length = bytes.length >> 1; i < length; i++) {
			int j = bytes.length - 1 - i;
			byte t = bytes[i];
			bytes[i] = bytes[j];
			bytes[j] = t;
		}
		return bytes;
	}

	public static int bigEndianInteger(byte[] bytes, int offset, int length) {
		int result = 0;
		for (int i = offset; i < (offset + length); i++) {
			byte b = bytes[i];
			result = (result << 8) | (b >= 0 ? (int) b : (b + 256));
		}
		return result;
	}

	public static int littleEndianInteger(byte[] bytes, int offset, int length) {
		int result = 0;
		for (int i = offset + length - 1; i >= offset; i--) {
			byte b = bytes[i];
			result = (result << 8) | (b >= 0 ? (int) b : (b + 256));
		}
		return result;
	}

	// public static byte readByte(ByteBuf src) {
	// return (byte) src.readUnsignedByte();
	// }

	public static short readUnsignedByte(ByteBuf src) {
		return src.readUnsignedByte();
	}

	// public static int readInt(ByteBuf src, int bits) {
	// int result = 0;
	// for (int i = 0; i < bits; ++i) {
	// result |= (src.readByte() << (i << 3));
	// }
	// return result;
	// }

	public static int readUnsignedInt(ByteBuf src, int bits) {
		int result = 0;
		for (int i = 0; i < bits; ++i) {
			result |= (src.readUnsignedByte() << (i << 3));
		}
		return result;
	}

	// public static long readLong(ByteBuf src, int bits) {
	// long result = 0;
	// for (int i = 0; i < bits; ++i) {
	// result |= (src.readByte() << (i << 3));
	// }
	// return result;
	// }

	public static long readUnsignedLong(ByteBuf src, int bits) {
		long result = 0;
		for (int i = 0; i < bits; ++i) {
			result |= (src.readUnsignedByte() << (i << 3));
		}
		return result;
	}

	public static String readZeroTerminatedString(ByteBuf src) {

		src.markReaderIndex();// 先标记
		int length = 0;
		while ('\0' != src.readByte()) {// 可见字符
			// 一直循环
			length++;
		}
		length++;// 这个长度包含\0
		// 找到了\0 的长度为length
		src.resetReaderIndex();// 恢复
		byte[] str = new byte[length];
		src.readBytes(str);
		// 之前有个bug,
		return new String(str, 0, length - 1);
	}

	public static String readSpecifiedLengthString(ByteBuf src, int length) {

		byte[] str = new byte[length];
		src.readBytes(str);
		return new String(str, 0, length);
	}

	public static byte[] readSpecifiedLengthBytes(ByteBuf src, int length) {

		byte[] str = new byte[length];
		src.readBytes(str);
		return str;
	}

	/**
	 * Format (first-byte-based):<br/>
	 * 0-250 - The first byte is the number (in the range 0-250). No additional
	 * bytes are used.<br/>
	 * 251 - SQL NULL value<br/>
	 * 252 - Two more bytes are used. The number is in the range 251-0xffff.
	 * <br/>
	 * 253 - Three more bytes are used. The number is in the range
	 * 0xffff-0xffffff.<br/>
	 * 254 - Eight more bytes are used. The number is in the range
	 * 0xffffff-0xffffffffffffffff.
	 */
	public static Number readVariableNumber(ByteBuf value) {
		// 读取变长的整数
		int b = ByteUtils.readUnsignedByte(value);
		if (b < 251) {
			return b;
		} else if (b == 251) {
			return null;
		} else if (b == 252) {
			return ByteUtils.readUnsignedLong(value, 2);
		} else if (b == 253) {
			return ByteUtils.readUnsignedLong(value, 3);
		} else if (b == 254) {
			return ByteUtils.readUnsignedLong(value, 8);
		}
		return null;
	}

	// 下面的函数主要用来写
	public static byte[] writeByte(byte value, int length) {
		byte[] result = new byte[length];
		for (int i = 0; i < length; i++) {
			// 小端模式
			result[i] = (byte) (0x000000FF & (value >>> (i << 3)));
		}
		return result;
	}

	public static byte[] writeInt(int value, int length) {
		byte[] result = new byte[length];
		for (int i = 0; i < length; i++) {
			// 小端模式
			result[i] = (byte) (0x000000FF & (value >>> (i << 3)));
		}
		return result;
	}

	public static byte[] writeLong(long value, int length) {
		byte[] result = new byte[length];
		for (int i = 0; i < length; i++) {
			// 小端模式
			result[i] = (byte) (0x000000FF & (value >>> (i << 3)));
		}
		return result;
	}

	public static byte[] writeString(String value) {// 最后要以\0结尾
		value += "\0";
		return value.getBytes();
	}

	// 借鉴了mysql-binlog的代码
	/**
	 * see mysql/sql/password.c scramble(...)
	 */
	public static byte[] passwordCompatibleWithMySQL411(String password, String salt) {
		MessageDigest sha;
		try {
			sha = MessageDigest.getInstance("SHA-1");
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
		byte[] passwordHash = sha.digest(password.getBytes());
		return xor(passwordHash, sha.digest(union(salt.getBytes(), sha.digest(passwordHash))));
	}

	public static byte[] union(byte[] a, byte[] b) {
		byte[] r = new byte[a.length + b.length];
		System.arraycopy(a, 0, r, 0, a.length);
		System.arraycopy(b, 0, r, a.length, b.length);
		return r;
	}

	public static byte[] xor(byte[] a, byte[] b) {
		byte[] r = new byte[a.length];
		for (int i = 0; i < r.length; i++) {
			r[i] = (byte) (a[i] ^ b[i]);
		}
		return r;
	}

	public static int availableWithChecksumLength(ByteBuf src, int checksumLength) {
		return src.readableBytes() - checksumLength;
	}

	public static boolean compare(byte[] array0, byte[] array1) {
		int index0 = 0;
		int index1 = 0;
		for (; index0 <= array0.length - 1 && index1 <= array1.length - 1; index0++, index1++) {
			if (array0[index0] != array1[index1]) {
				break;
			}
		}
		boolean success = (index0 == array0.length && index1 == array1.length);
		return success;
	}

}
